/*
 * File Name:               MessageHandler.cs
 *
 * Description:             编辑器中显示任意对象辅助工具
 * Author:                  lisiyu <576603306@qq.com>
 * Create Date:             2016/04/26
 */

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;

namespace UnityEditor
{
    public static class ObjectExtension
    {
        public static object display(this object src, string description, int offset = 0, bool onlyNickName = false, bool autoCreate = true, bool displayFileds = true, bool displayProperties = true, bool editMode = true, bool onlyValueType = false, bool box = true)
        {
            EditorGUILayout.BeginVertical(new GUIStyle() { margin = new RectOffset(offset == 0 ? 0 : 16, 0, 0, 0) });
            if (src is Array)
            {
                EditorGUILayout.LabelField(description + "  (Array)");

                var array = src as Array;
                var showVert = array.Length > 0;
                if (showVert)
                {
                    EditorGUILayout.BeginVertical();
                }

                int removeID = -1;
                for (int i = 0; i < array.Length; i++)
                {
                    EditorGUILayout.BeginHorizontal("box");
                    EditorGUILayout.LabelField("" + i, GUILayout.Width(20));
                    if (editMode && GUILayout.Button("-", GUILayout.Width(20)))
                    {
                        removeID = i;
                        break;
                    }


                    var v = array.GetValue(i);
                    if (v == null) continue;
                    v = v.display(v.GetType().Name, offset + 1, onlyNickName, autoCreate, displayFileds, displayProperties, editMode);
                    if (editMode)
                        array.SetValue(v, i);
                    EditorGUILayout.EndHorizontal();
                }

                if (removeID != -1)
                {
                    var elementType = array.GetType().GetElementType();
                    var lstType = typeof(List<>).MakeGenericType(new Type[] { elementType });
                    var lst = (IList)Activator.CreateInstance(lstType, null);
                    for (int i = 0, count = array.Length; i < count; i++)
                    {
                        lst.Add(array.GetValue(i));
                    }
                    lst.RemoveAt(removeID);

                    src = (Array)lstType.GetMethod("ToArray").Invoke(lst, null);

                    lstType = null;
                    lst.Clear();
                }

                if (editMode && GUILayout.Button("+    " + description))
                {
                    var elementType = array.GetType().GetElementType();
                    var lstType = typeof(List<>).MakeGenericType(new Type[] { elementType });
                    var lst = (IList)Activator.CreateInstance(lstType, null);
                    for (int i = 0, count = array.Length; i < count; i++)
                    {
                        lst.Add(array.GetValue(i));
                    }
                    lst.Add(elementType == typeof(string)
                        ? string.Empty
                        : elementType.Assembly.CreateInstance(elementType.FullName));

                    src = lstType.GetMethod("ToArray").Invoke(lst, null);

                    lstType = null;
                    lst.Clear();
                }

                if (showVert)
                {
                    EditorGUILayout.EndVertical();
                }

                EditorGUILayout.EndVertical();
                return src;
            }
            else if (src is IList) // 注意：数组类型也属于IList
            {
                EditorGUILayout.LabelField(description + "  (List)");

                var lst = src as IList;
                var showVert = lst.Count > 0;
                if (showVert)
                {
                    EditorGUILayout.BeginVertical();
                }

                int removeID = -1;
                for (int i = 0; i < lst.Count; i++)
                {
                    EditorGUILayout.BeginHorizontal("box");
                    EditorGUILayout.LabelField("" + i, GUILayout.Width(20));
                    if (editMode && GUILayout.Button("-", GUILayout.Width(20)))
                    {
                        removeID = i;
                        break;
                    }


                    var tmpValue = lst[i].display(lst[i].GetType().Name, offset + 1, onlyNickName, autoCreate, displayFileds, displayProperties, editMode);
                    if (tmpValue == null) continue;
                    lst[i] = tmpValue;
                    EditorGUILayout.EndHorizontal();
                }

                if (removeID != -1)
                {
                    lst.RemoveAt(removeID);
                }

                if (editMode && GUILayout.Button("+    " + description))
                {
                    var type = lst.GetType().GetGenericArguments()[0];
                    lst.Add(type == typeof(string)
                        ? string.Empty
                        : type.Assembly.CreateInstance(type.FullName));
                }

                if (showVert)
                {
                    EditorGUILayout.EndVertical();
                }

                EditorGUILayout.EndVertical();
                return src;
            }
            else if (src is IXProperty)
            {
                var field = src.GetType().GetField("_Value", BindingFlags.GetField | BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
                var value = field.GetValue(src);
                value = value.display(description, 0, onlyNickName, autoCreate, displayFileds, displayProperties, editMode);
                if (editMode)
                    field.SetValue(src, value);
                EditorGUILayout.EndVertical();
                return src;
            }
            else if (src is int || src is long || src is string || src is byte || src is Enum || src is bool || src is XNumber || src == null || src is XVector3 || src is XVector2)
            {
                if(box)
                    EditorGUILayout.BeginHorizontal("box");
                else
                    EditorGUILayout.BeginHorizontal();
                EditorGUILayout.LabelField(description);

                if (src is int)
                {
                    src = EditorGUILayout.IntField((int)src);
                }
                else if (src is long)
                {
                    src = long.Parse(EditorGUILayout.TextField(src.ToString()));
                }
                else if (src is string)
                {
                    src = EditorGUILayout.TextField(src.ToString());
                    if (src == null)
                        src = "";
                }
                else if (src is byte)
                {
                    src = (byte)EditorGUILayout.IntField((byte)src);
                }
                else if (src is Enum)
                {
                    src = EditorGUILayout.EnumPopup(src as Enum);
                }
                else if (src is bool)
                {
                    src = EditorGUILayout.Toggle((bool)src);
                }
                else if (src is XNumber)
                {
                    float value = (XNumber)src;
                    value = EditorGUILayout.FloatField(value);
                    src = (XNumber)value;
                }
                else if (src is XVector3)
                {
                    XVector3 value = (XVector3)src;
                    EditorGUILayout.LabelField("x", GUILayout.Width(10));
                    value.x = EditorGUILayout.FloatField(value.x);
                    EditorGUILayout.LabelField("y", GUILayout.Width(10));
                    value.y = EditorGUILayout.FloatField(value.y);
                    EditorGUILayout.LabelField("z", GUILayout.Width(10));
                    value.z = EditorGUILayout.FloatField(value.z);
                    src = value;
                }
                else if(src is XVector2)
                {
                    XVector2 value = (XVector2)src;
                    EditorGUILayout.LabelField("x", GUILayout.Width(10));
                    value.x = EditorGUILayout.FloatField(value.x);
                    EditorGUILayout.LabelField("y", GUILayout.Width(10));
                    value.y = EditorGUILayout.FloatField(value.y);
                    src = value;
                }

                EditorGUILayout.Space();
                EditorGUILayout.EndHorizontal();

                EditorGUILayout.EndVertical();
                return src;
            }
            else // if (src is ProtoBuf.IExtensible)
            {
                EditorGUILayout.LabelField(description);

                if (displayFileds)
                {
                    var fields = src.GetType().GetFields(BindingFlags.GetField | BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
                    EditorGUILayout.BeginVertical();
                    int _index = 0;
                    foreach (var tmp in fields)
                    {
                        if (onlyValueType && !tmp.FieldType.IsValueType)
                            continue;

                        string name = tmp.Name;
                        var attributes = tmp.GetCustomAttributes(typeof(NickNameAttribute), true);
                        if (attributes != null && attributes.Length > 0)
                        {
                            name = (attributes[0] as NickNameAttribute).NickName;
                        }
                        else if (onlyNickName)
                        {
                            continue;
                        }

                        var obj = tmp.GetValue(src);
                        if (obj == null)
                        {
                            if (tmp.FieldType == typeof(string))
                            {
                                obj = string.Empty;
                            }
                            else if (tmp.FieldType.IsArray)
                            {
                                obj = Activator.CreateInstance(tmp.FieldType, 0);
                            }
                            else if (tmp.FieldType.IsInterface || tmp.FieldType.IsAbstract)
                            {
                                continue;
                            }
                            else
                            {
                                if (autoCreate)
                                    obj = Activator.CreateInstance(tmp.FieldType);
                            }
                        }

                        obj = obj.display(name, offset + 1, onlyNickName, autoCreate, displayFileds, displayProperties, editMode, box:_index++ % 2 == 0);

                        //if (obj is IList && !(obj is Array)) continue;
                        if (editMode)
                            tmp.SetValue(src, obj);
                    }
                    EditorGUILayout.EndVertical();
                }


                if (displayProperties)
                {
                    var properties = src.GetType().GetProperties(BindingFlags.GetField | BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
                    EditorGUILayout.BeginVertical();
                    int _index = 0;
                    foreach (var tmp in properties)
                    {
                        if (onlyValueType && !tmp.PropertyType.IsValueType)
                            continue;

                        string name = tmp.Name;
                        var attributes = tmp.GetCustomAttributes(typeof(NickNameAttribute), true);
                        if (attributes != null && attributes.Length > 0)
                        {
                            name = (attributes[0] as NickNameAttribute).NickName;
                        }
                        else if (onlyNickName)
                        {
                            continue;
                        }

                        var obj = tmp.GetValue(src, null);
                        if (obj == null)
                        {
                            if (tmp.PropertyType == typeof(string))
                            {
                                obj = string.Empty;
                            }
                            else if (tmp.PropertyType.IsArray)
                            {
                                obj = Activator.CreateInstance(tmp.PropertyType, 0);
                            }
                            else if (tmp.PropertyType.IsInterface || tmp.PropertyType.IsAbstract)
                            {
                                continue;
                            }
                            else
                            {
                                if (autoCreate)
                                    obj = Activator.CreateInstance(tmp.PropertyType);
                            }
                        }

                        obj = obj.display(name, offset + 1, onlyNickName, autoCreate, displayFileds, displayProperties, editMode, box: _index++ % 2 == 0);

                        if (obj is IList && !(obj is Array)) continue;
                        var setMethod = tmp.GetSetMethod();
                        if (setMethod == null) continue;
                        if (editMode)
                            tmp.SetValue(src, obj, null);
                    }
                    EditorGUILayout.EndVertical();
                }

                EditorGUILayout.EndVertical();
                return src;
            }
        }
    }
}